<?php
/**
 * 应用类
 *
 * 通过该类的单例可以查到整个应用的一些信息
 * 
 * @package if
 * @subpackage core
 * @since 1.0
 */
class IApplication extends IObject {
	private static $instance;
	private static $started = false;
	private $root;
	private $locale;
	private static $modules = array();
	private $directories = array(
		"root" => "ipa",
		"app" => "app",
		"config" => "app/config",
		"controllers" => "app/controllers",
		"models" => "app/models",
		"helpers" => "app/helpers",
		"filters" => "app/filters",
		"views" => "app/views",
		"components" => "components",
		"modules" => "lib/modules",
		
		"userControllers" => array(),
		"userModels" => array(),
		"userHelpers" => array(),
		"userFilters" => array(),
	);
	
	private $router;
	private $encryptKey;
	/**
	 * Enter description here...
	 *
	 * @var IVM
	 */
	private $vm;
	
	/**
	 * Enter description here...
	 *
	 * @since 1.1.2
	 */
	private  function __construct() {
		$this->vm = IVM::singleton();
	}
	
	/**
	 * 取得当前类的单例
	 *
	 * @return IApplication
	 * @since 1.0
	 */
	static function singleton() {
		if (!self::$instance) {
			self::$instance = new self;
		}
		return self::$instance;
	}
	
	/**
	 * 取得当前框架应用的版本
	 *
	 * @return string
	 * @since 1.0
	 */
	function version() {
		return IF_VERSION;
	}
	
	/**
	 * 设置应用的根路径
	 *
	 * @param string $root 根路径
	 * @since 1.0
	 */
	function setRoot($root) {
		$this->root = $root;
	}
	
	/**
	 * 取得当前应用的根路径
	 *
	 * @return string
	 * @since 1.0
	 */
	function root() {
		$root = null;
		if ($this->root && if_is_absolute_path($this->root)) {
			return $this->root;
		}
		else if (defined("IF_APP_ROOT")) {
			$root = IF_APP_ROOT;
		}
		else if (isset($this->directories["root"])) {
			$root = $this->directories["root"];
		}
		
		if (is_dir($root)) {
			$this->root = realpath($root);
		}
		else {
			$this->root = $root;
		}
		return $this->root;
	}
	
	/**
	 * 初始化
	 *
	 * @since 1.0
	 */
	private static function initialize() {
		$app = IApplication::singleton();

		//分析组件
		if ($GLOBALS["IF_HAS_COMPONENT"]) {
			$app->setRoot($app->directory("components") . "/" . $GLOBALS["IF_COMPONENT_DIR"]);
		}
		
		//定义IF_APP_ROOT
		if (!defined("IF_APP_ROOT")) {
		 	define("IF_APP_ROOT", $app->root());
		}
		if (!defined("IF_MODULE_WORKPLACE")) {
			define("IF_MODULE_WORKPLACE", IF_APP_ROOT . "/workplace/module");
		}
		
		$app->_loadIncludePaths();
		$app->_loadClassIndexes();
		$app->_loadClassPaths();
		$app->_loadConstants();
		$app->_loadFunctions();
		$app->_loadErrorHandler();
		
		//datezone added @ v1.0.2
		if (version_compare(PHP_VERSION, "5.1.0") >= 0 && !ini_get("date.timezone")) {
			date_default_timezone_set("Asia/Chongqing");
		}
	}
	
	/**
	 * 启动应用
	 * 
	 * 如果不开启路由，而且不支持register_shutdown_function()函数时，则需要手工在程序的末尾写入
	 *    if_commit_compiler(IF_COMPILER_DIR);
	 * 来关闭编译器
	 *
	 * @param boolean $router 是否开启路由
	 * @since 1.0
	 */
	static function start($router = true) {		
		//启动编译
		$app = self::singleton();
		
		//初始化
		self::initialize();
		
		//设置已启动
		self::$started = true;
		
		if ($router) {
			$router = new IRouter();
			$app->setRouter($router);
			$router->start();
			
			//结束
			if_commit_compiler(IF_COMPILER_DIR);
		}
	}
	
	/**
	 * 判断当前应用是否已启动
	 *
	 * @return boolean
	 * @since 1.0
	 */
	static function started() {
		return self::$started;
	}
	
	/**
	 * 取得相对于应用主目录的文件|目录对象
	 *
	 * @param string $path 路径
	 * @return IFile
	 * @since 1.0
	 */
	function relativeFile($path) {
		if (if_is_absolute_path($path)) {
			return new IFile($path);
		}
		return new IFile($this->root() . "/" . $path);
	}
	
	/**
	 * 取得当前应用的本地化对象
	 *
	 * @return ILocale
	 * @since 1.0
	 */
	function locale() {
		if (!$this->locale) {
			$this->locale = ILocale::auto();
		}
		return $this->locale;
	}
	
	/**
	 * 判断是否加载了某个模块
	 *
	 * @param string $module 模块名
	 * @return boolean
	 * @since 1.0
	 */
	static function hasModule($module) {
		return isset(self::$modules[$module]);
	}
	
	/**
	 * 取得配置所在的目录
	 *
	 * @return string
	 * @since 1.0
	 */
	function configDir() {
		return $this->directory("config");
	}
	
	/**
	 * 取得某个类型的目录
	 *
	 * @param string $type 目录类型
	 * @param boolean $absolute 是否得到绝对路径
	 * @return string
	 * @since 1.0
	 */
	function directory($type, $absolute = true) {
		if (!isset($this->directories[$type])) {
			return null;
		}
		$root = $this->root();
		if ($type == "root") { 
			return $root;
		}
		$directory = $this->directories[$type];
		if (is_array($directory)) {
			foreach ($directory as $key => $dir) {
				if (if_is_absolute_path($dir)) {
					continue;
				}
				$directory[$key] = ($absolute ? $this->root() . "/" : "") . $dir;
			}
			return $directory;
		}
		else {
			if (if_is_absolute_path($directory)) {
				return $directory;
			}
			return ($absolute ? $this->root() . "/" : "") . $directory;;
		}
	}
	
	/**
	 * 取得目录配置
	 *
	 * @return array
	 * @since 1.0
	 */
	function directories() {
		return $this->directories;
	}
	
	/**
	 * 配置目录
	 *
	 * @param array $directories 目录
	 * @since 1.0
	 */
	function setDirectories(array $directories) {
		$this->directories = $directories;
	}
	
	/**
	 * 加载目录配置
	 *
	 * @param array $directories 目录
	 * @since 1.0
	 */
	static function load(array $directories) {
		$app = self::singleton();
		$oldDirectories = $app->directories();
		$directories = array_merge($oldDirectories, $directories);
		$app->setDirectories($directories);
	}
	
	/**
	 * 取得当前加载的模块
	 * 
	 * 此时的模块路径已经被处理成绝对路径
	 *
	 * @return array
	 * @since 1.0
	 */
	function modules() {
		$modules = if_array_from_compiler("modules");
		if (!empty($modules)) {
			return $modules;
		}
		$modules = array();
		
		//读取配置中的模块
		$innerModules = o("@app.modules");
		$_3rdModules = o("@app.3rd_modules");
		
		//处理路径
		foreach ($innerModules as $name=>$dir) {
			$dir = IF_FW_DIR . "/" . $dir;
			if (!is_dir($dir)) {
				throw new IException("cannot read dir '{$dir}' of {$name} module", 0x100007);
			}
			$modules[$name] = $dir;
		}
		foreach ($_3rdModules as $name=>$dir) {
			if (!if_is_absolute_path($dir)) {
				$dir = $this->relativeFile($dir)->path();
			}
			if (!is_dir($dir)) {
				throw new IException("cannot read dir '{$dir}' of {$name} module", 0x100008);
			}
			$modules[$name] = $dir;
		}

		
		//读取目录中的模块
		$dir = new IFile($this->directory("modules"));
		$files = $dir->lists();
		foreach ($files as $file) {
			if ($file->isDir() && $file->name() != ".svn") {
				$name = $file->name();
				list($name) = explode("_", $name);
				if (!isset($modules[$name])) {
					$modules[$name] = $file->path();
				}
			}
		}
		if_array_to_compiler("modules", $modules);
		return $modules;
	}
	
	/**
	 * 设置路由
	 *
	 * @param IRouter $router 路由
	 * @since 1.0
	 */
	function setRouter(IRouter $router) {
		$this->router = $router;
	}
	
	/**
	 * 路由
	 *
	 * @return IRouter
	 * @since 1.0
	 */
	function router() {
		return $this->router;
	}
	
	/**
	 * 动态加载一个模块
	 *
	 * @param string $name 模块名
	 * @param string $dir 模块所在路径
	 * @since 1.0
	 */
	static function loadModule($name, $dir) {
		global $COMPILED_CONFIG;
		
		if (!if_is_absolute_path($dir)) {
			$dir = IF_APP_ROOT . "/" . $dir;
		}
		$config = IConfig::module("app");
		$_3rdModules = $config->get("3rd_modules");
		if (!is_array($_3rdModules)) {
			$_3rdModules = array();
		}
		$_3rdModules[$name] = $dir;
		$config->set("3rd_modules", $_3rdModules);
		self::$modules[$name] = $dir;
		
		//加载_class.php
		$vm = IVM::singleton();
		$classIndexFile = $dir . "/_class.php";
		$classes = $vm->loadFile($classIndexFile);
		if (is_array($classes)) {
			$classes = self::_loadClasses($classes, $dir);
			$vm->addClassIndexes($classes, true);
		}
		
		//清空
		$COMPILED_CONFIG = array();
	}
	
	/**
	 * 系统自动生成的加密数据使用的键值
	 * 
	 * 要想使加密生效，必须要让和framework同级的 tmp/ 目录是可写的，否则就
	 * 无法创建加密使用的键
	 *
	 * @return string
	 * @since 1.1.0
	 */
	function encryptKey() {
		return $this->encryptKey;
	}
	
	/**
	 * 加密数据
	 *
	 * @param mixed $data 可序列化的数据
	 * @return string
	 * @since 1.1.0
	 * @see decrypt
	 */
	function encrypt($data) {
		$data = array($data);
		$data = serialize($data);
		if (function_exists("gzcompress")) {
			$data = gzcompress($data,  9);
		}
		$newKey = md5($this->encryptKey . $data);
		$data = base64_encode($newKey . $data);
		return $data;
	}
	
	/**
	 * 解密数据
	 *
	 * @param string $data 加密后的数据
	 * @return mixed
	 * @since 1.1.0
	 * @see encrypt
	 */
	function decrypt($data) {
		$data = @base64_decode($data);
		if (!$data) {
			return null;
		}
		$key = substr($data, 0, 32);
		$data = substr($data, 32);
		if (md5($this->encryptKey . $data) != $key) {
			return null;
		}
		if (function_exists("gzuncompress")) {
			$data = gzuncompress($data);
		}
		$data = unserialize($data);
		return $data[0];
	}
	
	/**
	 * 加载模块的Class列表
	 *
	 * @param array $classes
	 * @param string $dir
	 * @return array
	 * @since 1.1.1
	 */
	private static function _loadClasses($classes, $dir) {
		$except = array();
		$ret = array();
		foreach ($classes as $key => $class) {//v1.1.1 处理 ClassName 和 a.b.ClassName
			if (class_exists("IFile")) {
				if (is_int($key)) {
					if (strstr($class, "*")) {
						foreach(f($dir)->lists($class, true, true) as $file) {
							$filename = str_replace(array(".php", ".class.php", ".inc.php", ".inc"), "", $file->name());
							if (if_is_valid_variable_name($filename) && preg_match("/^[A-Z]/", $filename)) {
								$ret[$filename] = $file->path();
							}
						}
					}
					else {
						$class = preg_replace("/\.php$/i", "", $class);//去除扩展名
						$ret[if_class_name($class)] = str_replace(".", "/", $class) . ".php";
					}
				}
				elseif ($key == "except") {
					if (!is_array($class)) {
						$class = array($class);
					}
					foreach ($class as $_class) {
						foreach(f($dir)->lists($_class, true, true) as $file) {
							$filename = str_replace(array(".php", ".class.php", ".inc.php", "inc"), "", $file->name());
							if (if_is_valid_variable_name($filename) && preg_match("/^[A-Z]/", $filename)) {
								$except[$filename] = $file->path();
							}
						}
					}
				}
				else {
					$ret[$key] = $class;
				}
			}
			else {
				$ret[$key] = $class;
			}
		}
		
		foreach ($ret as $class => $path) {
			if (!if_is_absolute_path($path)) {
				$path = realpath($dir . DIRECTORY_SEPARATOR . $path);
				if ($path) {
					$ret[$class] = $path;
				}
				else {
					unset($ret[$class]);
				}
			}
			else {
				$path = realpath($path);
				if ($path) {
					$ret[$class] = $path;
				}
			}
		}
		foreach ($except as $class => $path) {
			if (!if_is_absolute_path($path)) {
				$except[$class] = realpath($dir . DIRECTORY_SEPARATOR . $path);
			}
			else {
				$except[$class] = realpath($path);
			}
		}

		return array_diff($ret, $except);
	}
	
	/**
	 * 添加一个应用结构中的目录
	 *
	 * @param string $type 类型，如controllers, models
	 * @param string|array $directory 一个或一组路径
	 * @since 1.1.2
	 */
	function addDirectory($type, $directory) {
		$dir = $this->directory($type);
		if (!is_array($dir) && if_empty($dir)) {
			$this->directories[$type] = $directory;
		}
		else if (is_array($dir)) {
			if (is_array($directory)) {
				$this->directories[$type] = array_merge($dir, $directory);
			}
			else {
				$dir[] = $directory;
				$this->directories[$type] = $dir;
			}
		}
		else {
			if (is_array($directory)) {
				array_unshift($directory, $dir);
				$this->directories[$type] = $directory;
			}
			else {
				$this->directories[$type] = array($dir, $directory);
			}
		}
	}
	
	private function _loadModules() {
		//加载directories
		if (!isset($GLOBALS["COMPILED_CONFIG"]["IF_DIRECTORIES"])) {//v1.1.2
			$modules = $this->modules();
			
			foreach ($modules as $name => $dir) {
				$moduleConfig = $dir . "/_module.php";
				if (is_file($moduleConfig)) {
					$module = new IModule($moduleConfig);
					if ($module->isInstalled()) {
						if (is_dir($dir . "/_deploy/controllers")) {
							$this->addDirectory("userControllers", $dir . "/_deploy/controllers");
						}
						if (is_dir($dir . "/_deploy/models")) {
							$this->addDirectory("userModels", $dir . "/_deploy/models");
						}
						if (is_dir($dir . "/_deploy/helpers")) {
							$this->addDirectory("userHelpers", $dir . "/_deploy/helpers");
							$this->vm->addClassPath($dir . "/_deploy/helpers");
						}
						if (is_dir($dir . "/_deploy/filters")) {
							$this->addDirectory("userFilters", $dir . "/_deploy/filters");
							$this->vm->addClassPath($dir . "/_deploy/filters");
						}
					}
				}
			}
			$GLOBALS["COMPILED_CONFIG"]["IF_DIRECTORIES"] = $this->directories();
			
			//加载模块
			self::$modules = $modules;
			foreach ($modules as $name => $dir) {
				//加载classes
				$configFile = $dir . "/_class.php";
				if (is_file($configFile)) {
					$classes = $this->vm->loadFile($configFile);
					if (is_array($classes)) {
						$classes = self::_loadClasses($classes, $dir);
						$this->vm->addClassIndexes($classes, true);
					}
				}
				
				//安装assets
				foreach (array($dir . "/assets", $dir . "/_assets") as $assetsDir) {
					if (is_dir($assetsDir)) {
						$source = new IFile($assetsDir);
						$dest = strstr(IF_APP_ROOT, "ipa") ? dirname(IF_APP_ROOT) . "/assets" : IF_APP_ROOT . "/public/assets";//@todo 应该更严谨
						if (!is_dir($dest)) {
							@mkdir($dest, 0777, true);
						}
						if (is_writable($dest)) {
							foreach ($source->lists(null, false, true) as $file) {
								$file->copyTo($dest, null, true);
							}
						}
						else {
							exit("Can not deploy assets, please make {$dest} writeable");
						}
					}
				}
			}
			
			//检测安装程序
			IModuleManager::singleton()->detect();
		}
		else {
			$this->setDirectories($GLOBALS["COMPILED_CONFIG"]["IF_DIRECTORIES"]);
		}
	}
	
	private function _loadModels() {
		//加载所有models
		foreach (array($this->directory("models"), $this->directory("userModels")) as $modelDirectories) {
			if (!is_array($modelDirectories)) {
				$modelDirectories = array($modelDirectories);
			}
			foreach ($modelDirectories as $modelDirectory) {
				$models = f($modelDirectory)->lists("*.php", true, true);
				$reged = array();
				foreach ($models as $file) {
					$name = $file->name();
					$model = substr($name, 0, strpos($name, "."));
					if (!in_array($model, $reged)) {
						$this->vm->addClassIndexes(array($model => $file->absPath()), true);
						$reged[] = $model;
					}
				}
			}
		}
	}
	
	private function _loadClassPaths() {
		//class_path
		if (!o("@app.IF_LOADED")) {
			$classPaths = o("@app.class_path");
			if (!$classPaths) {
				$classPaths = array();
			}
			foreach ($classPaths as $key=>$path) {
				if (!if_is_absolute_path($path)) {
					$path = $this->root() . "/" . $path;
				}
				$classPaths[$key] = $path;
			}
			//加入app下目录 v1.1.0
			if (is_dir($this->directory("app"))) {
				$appdir = opendir($this->directory("app"));
				while (($dir = readdir($appdir)) !== false) {
					$dir = $this->directory("app") . "/" . $dir;
					if (!if_is_absolute_path($dir)) {
						$dir = $this->root() . "/" . $dir;
					}
					if (is_dir($dir) && preg_match("/^[\w\-]+$/", basename($dir)) && !in_array(basename($dir), array("config", "views"))) {
						$classPaths[] = $dir;
					}
				}
				closedir($appdir);
				if (!empty($classPaths)) {
					$this->vm->addClassPath($classPaths);
					o("@app.class_path", $this->vm->classPaths());
					o("@app.IF_LOADED", 1);
				}
			}
		}
		else {
			$classPaths = o("@app.class_path");
			if (is_array($classPaths)) {
				$this->vm->addClassPath($classPaths);
			}
		}
	}
	
	private function _loadClassIndexes() {
		//看看缓存中有没有
		if (isset($GLOBALS["COMPILED_CONFIG"]["IF_CLASS_INDEX"]) && is_array($GLOBALS["COMPILED_CONFIG"]["IF_CLASS_INDEX"])) {
			$this->vm->addClassIndexes($GLOBALS["COMPILED_CONFIG"]["IF_CLASS_INDEX"]);
			self::$modules = $this->modules();
			$this->_loadModules();
			$this->_loadModels();
		}
		else {			
			//加载框架核心的class列表
			$classes = $this->vm->loadFile(IF_FW_DIR . "/config/_class.php");
			$classes = self::_loadClasses($classes, IF_FW_DIR);
			$this->vm->addClassIndexes($classes, true);

			$this->_loadModules();
			$this->_loadModels();
	
			//更新class index
			$GLOBALS["COMPILED_CONFIG"]["IF_MODIFIED"] = true;
			$GLOBALS["COMPILED_CONFIG"]["IF_CLASS_INDEX"] = $this->vm->classIndexes();
		}
	}
	
	private function _loadConstants() {
		//有没有encryptKey
		if (!isset($GLOBALS["COMPILED_CONFIG"]["IF_CONST"]["ENCRYPT_KEY"])) {
			//write file
			$constFile = IF_COMPILER_DIR . "/~" . urlencode(if_domain_name()) . ".const.php";
			if (is_file($constFile)) {
				$GLOBALS["COMPILED_CONFIG"]["IF_CONST"] = require($constFile);
				$this->encryptKey = $GLOBALS["COMPILED_CONFIG"]["IF_CONST"]["ENCRYPT_KEY"];
			}
			else {
				$constConfig = array( 
					"ENCRYPT_KEY" => md5(uniqid(rand(1, 10000) . rand(1, 10000), true)) 
				);
				if (if_write_file($constFile, "<?php\n return " . var_export($constConfig, true) . ";\n?>")) {
					$GLOBALS["COMPILED_CONFIG"]["IF_CONST"] = $constConfig;
					$this->encryptKey = $constConfig["ENCRYPT_KEY"];
				}
				else {
					$this->encryptKey = md5(php_uname());
				}
			}
		}
		else {
			$this->encryptKey = $GLOBALS["COMPILED_CONFIG"]["IF_CONST"]["ENCRYPT_KEY"];
		}
	}
	
	private function _loadFunctions() {
		//加载用户自定义函数
		$userFuncFile = $this->directory("helpers") . "/functions.php";
		if (is_file($userFuncFile)) {
			require($userFuncFile);
		}
	}
	
	private function _loadIncludePaths() {
		//运行配置
		//include_path
		$includePaths = o("@app.include_path");
		if (is_array($includePaths)) {
			foreach ($includePaths as $key=>$path) {
				if (!if_is_absolute_path($path)) {
					$includePaths[$key] = $this->root() . "/" . $path;
				}
			}
			set_include_path(rtrim(get_include_path(), PATH_SEPARATOR) . PATH_SEPARATOR . implode(PATH_SEPARATOR, $includePaths));
		}
	}
	
	private function _loadErrorHandler() {
		//定义error-handler
		$errorHandler = new IErrorHandler();
		set_exception_handler(array($errorHandler, "handlerException"));
		set_error_handler(array($errorHandler, "handlerError"));
	}
}

?>